package quick_search

import (
	"fmt"
	"reflect"
	"strings"

	"gitee.com/mkwchecking/sr-core/yal/ystr"
	"gitee.com/mkwchecking/sr-core/yerr"
	"github.com/spf13/cast"
	"gorm.io/gorm"
)

type MatchType string

const (
	MatchTypeEqual          MatchType = "eq"  // ==
	MatchTypeNEqual         MatchType = "neq" // !== <>
	MatchTypeGreatThan      MatchType = "gt"  // >
	MatchTypeGreatEqualThan MatchType = "gte" // >=
	MatchTypeLessThan       MatchType = "lt"  // >
	MatchTypeLessEqualThan  MatchType = "lte" // >=
	MatchTypeLeftLike       MatchType = "n?"  // like '%value'
	MatchTypeRightLike      MatchType = "r?"  // like 'value%'
	MatchTypeLike           MatchType = "??"  // like '%value%'
	MatchTypeIn             MatchType = "in"  // in (?)
)

type IgnoreType string

const (

	// 数据为null是不做匹配
	IgnoreTypeNull IgnoreType = "null"

	// 数据为空是不做匹配: string="" , array=[]{} ,map = {}
	IgnoreTypeEmpty IgnoreType = "empty"

	// 数据为0是不做匹配
	IgnoreTypeZero IgnoreType = "zero"

	// 数据不可用时都不匹配(null || empty || zero)
	IgnoreTypeAllInvalidate IgnoreType = ""

	// 不忽略,数据为空也做匹配
	IgnoreTypeNone IgnoreType = "none"
)

type Field struct {
	// 字段分组标记, 所有字段相同组会合并 (Group1) and (Group2)
	// tag: gk
	GroupKey string

	// 表名
	// tag: table
	Table string

	// 对应数据库字段名,默认蛇形命令
	// tag: col
	Column string

	// 对应匹配规则,默认 eq
	// tag: match
	MatchType MatchType

	// 匹配数据
	Value interface{}

	// 忽略规则
	// ignore
	IgnoreType IgnoreType

	// 拓展查询指令:
	// tag: sql
	// 	eg:  "and $field = ?"
	// 上下文参数, 大小写敏感
	// 	 $COLUMN / $column : 字段名
	ExtraCmd string
}

func (f *Field) execute(db *gorm.DB) *gorm.DB {
	if f.Table == "" && db.Statement.Table != "" {
		f.Table = db.Statement.Table
	} else if f.Column == "" {
		return db
	}

	// 数据是否忽略
	if f.ignore() {
		return db
	}

	// 匹配规则
	switch f.MatchType {
	case MatchTypeEqual:
		db = db.Where(f.Column+" = ?", f.Value)
	case MatchTypeNEqual:
		db = db.Where(f.Column+" <> ?", f.Value)
	case MatchTypeGreatThan:
		db = db.Where(f.Column+" > ?", f.Value)
	case MatchTypeGreatEqualThan:
		db = db.Where(f.Column+" >= ?", f.Value)
	case MatchTypeLessThan:
		db = db.Where(f.Column+" < ?", f.Value)
	case MatchTypeLessEqualThan:
		db = db.Where(f.Column+" <= ?", f.Value)
	case MatchTypeLeftLike:
		db = db.Where(f.Column+" like ?", "%"+cast.ToString(f.Value))
	case MatchTypeRightLike:
		db = db.Where(f.Column+" like ?", cast.ToString(f.Value)+"%")
	case MatchTypeLike:
		db = db.Where(f.Column+" like ?", "%"+cast.ToString(f.Value)+"%")
	case MatchTypeIn:
		db = db.Where(f.Column+" in (?)", f.Value)
	default:
		fmt.Println("[ERROR] unknown MatchType:", f.MatchType)
	}

	return db
}

// ignore 是否忽略
func (f *Field) ignore() bool {
	switch f.IgnoreType {
	case IgnoreTypeNull, IgnoreTypeEmpty, IgnoreTypeZero, IgnoreTypeAllInvalidate:
		return f.isValueEmpty()
	case IgnoreTypeNone:
		return false
	}

	return true
}

func (f *Field) isValueEmpty() bool {
	if f.Value == nil {
		return true
	}

	switch v := f.Value.(type) {
	case string:
		return v == ""
	case int, int64, uint, uint64, float32, float64:
		return v == 0
	case []string:
		return len(v) == 0
	case []int:
		return len(v) == 0
	case []int64:
		return len(v) == 0
	case []uint:
		return len(v) == 0
	case []uint64:
		return len(v) == 0
	case []float32:
		return len(v) == 0
	case []float64:
		return len(v) == 0
	case []interface{}:
		return len(v) == 0
	case map[string]interface{}:
		return len(v) == 0
	}
	return false
}

type FieldGroup struct {
	GroupKey string

	Fields []*Field
}

func (g *FieldGroup) Execute(db *gorm.DB) *gorm.DB {
	for _, f := range g.Fields {
		db = f.execute(db)
	}
	return db
}

type Condition struct {
	groups map[string]*FieldGroup
}

func (c *Condition) AddField(f *Field) {
	if c.groups == nil {
		c.groups = make(map[string]*FieldGroup)
	}

	if fg, ok := c.groups[f.GroupKey]; ok {
		fg.Fields = append(fg.Fields, f)
	} else {
		c.groups[f.GroupKey] = &FieldGroup{
			GroupKey: f.GroupKey,
			Fields:   []*Field{f},
		}
	}
}

func (c *Condition) Execute(db *gorm.DB) *gorm.DB {
	for _, fg := range c.groups {
		db = fg.Execute(db)
	}
	return db
}

func ParseCondition(tag string, field reflect.StructField, val reflect.Value) (*Field, error) {
	args := strings.Split(tag, ";")

	c := &Field{
		Column: ystr.SnakeString(field.Name),
		Value:  val.Interface(),
	}

	for _, f := range args {
		kv := strings.Split(strings.TrimSpace(f), ":")
		if len(kv) != 2 {
			return nil, yerr.ErrBizParamInvalid.Error("tag declare error:(", tag, ")on Field:", f)
		}
		val := strings.TrimSpace(kv[1])
		switch strings.ToLower(kv[0]) {
		case "gk":
			c.GroupKey = val
		case "col":
			if val != "" {
				c.Column = val
			}
		case "match":
			c.MatchType = MatchType(val)
		case "ignore":
			c.IgnoreType = IgnoreType(val)
		case "sql":
			c.ExtraCmd = val
		case "table":
			c.Table = val
		}
	}
	return c, nil
}
